﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Super_Management_Mayhem.Control;
using Super_Management_Mayhem.Screens.Playing.AttackModel;
using Super_Management_Mayhem.Screens.Playing.Items;
using Super_Management_Mayhem.Screens.Playing.ObjectInterfaces;
using Super_Management_Mayhem.Screens.Playing.Players;
using Super_Management_Mayhem.Screens.Playing.Players.Manager;
using Super_Management_Mayhem.Screens.Playing.Points;
using Super_Management_Mayhem.Screens.Playing.Tiles;
using Transformable_Engine_v2.Engine.GameObjects.Containers;
using Transformable_Engine_v2.Engine.GameObjects.Containers.Transformable_controls;
using Transformable_Engine_v2.Engine.GameObjects.Primitives;
using Transformable_Engine_v2.Engine.Global;
using Transformable_Engine_v2.Engine.Graphics;
using Transformable_Engine_v2.Engine.Helper;
using Transformable_Engine_v2.Engine.Rendering;
using Transformable_Engine_v2.Engine.Transformable_Math.Geometry;

namespace Super_Management_Mayhem.Screens.Playing.Maps
{
    public abstract class BaseMap : Container
    {
        public enum ItemType
        {
            Shuriken,
            Bomb,
            Random,
            Torch
        }

        static readonly string[] WallCharacters = new[] { "E", "C", "D", "O" };
        static readonly string[] HoleCharacters = new[] { "X", "x" };

        protected readonly List<TE_Rectangle> Collisions;
        protected readonly List<TE_Rectangle> Holes;

        private readonly Container PointLayer;
        private readonly List<TE_Vector2> _spawnPoints;
        private readonly List<Warp> _warps;

        public List<PlayerContainer> Players { get; private set; } 

        protected Container BackgroundLayer;
        protected Container DebugCollisionLayer;
        protected Container ParticleLayer;
        protected Container PlayerLayer;
        protected Container TileLayer;
        protected Container WarpLayer;

        protected BaseMap(string mapName)
        {
            _spawnPoints = new List<TE_Vector2>();
            Collisions = new List<TE_Rectangle>();
            Holes = new List<TE_Rectangle>();
            Players = new List<PlayerContainer>();
            _warps = new List<Warp>();

            ViewHandler.AddView("mapView", new TE_Rectangle(0, 0, 2, 2));

            LoadSpriteSheet();

            BackgroundLayer = new Container();

            TileLayer = new Container();
            WarpLayer = new Container();
            AddTileLayerObjects();

            PlayerLayer = new Container();
            PointLayer = new Container();
            AddPointLayerObjects();
            AddTileLayerObjects();
            ReadMapCollisionData(mapName);
            ReadMapVisualData(mapName);
            AddCollisionLayerObjects();

            ParticleLayer = new Container();

            DebugCollisionLayer = new Container();
            if (Playscreen.SHOW_COLLISION)
                ShowCollisionRectangle();

            AddChildren(BackgroundLayer, TileLayer, WarpLayer, DebugCollisionLayer, PointLayer, PlayerLayer,
                        ParticleLayer);
            base.LoadContent();
        }

        public TE_Vector2 Size { get; protected set; }

        public abstract int StartingPoints { get; }

        public List<IPickable> PickableItems
        {
            get { return PlayerLayer.Children.OfType<IPickable>().ToList(); }
        }

        private void ReadMapVisualData(string mapName)
        {
            var mapData = new List<char[]>();
            using (var sr = new StreamReader("Content/Maps/" + mapName + ".vmap"))
            {
                while (!sr.EndOfStream)
                {
                    string readLine = sr.ReadLine();
                    if (readLine != null) mapData.Add(readLine.ToCharArray());
                }
            }

            for (int y = 0; y < mapData.Count; y++)
            {
                for (int x = 0; x < mapData[y].Length; x++)
                {
                    char c = mapData[y][x];

                    if (c != '-')
                    {
                        var area = new TE_Vector2(1, 1);

                        for (int xx = x + 1; xx < mapData[y].Length; xx++)
                        {
                            if (mapData[y][xx] == c)
                            {
                                area.X++;
                                mapData[y][xx] = '-';
                            }
                            else
                                break;
                        }
                        if (area.X == 1)
                        {
                            for (int yy = y + 1; yy < mapData.Count; yy++)
                            {
                                if (mapData[yy][x] == c)
                                {
                                    area.Y++;
                                    mapData[yy][x] = '-';
                                }
                                else
                                    break;
                            }
                        }

                        if (area.X > 1 || area.Y > 1)
                        {
                            AddWall(x*2, y*2, GetSpriteFromChar(c), area*2);
                        }
                        else
                        {
                            AddWall(x*2, y*2, GetSpriteFromChar(c));
                        }

                        mapData[y][x] = '-';
                    }
                }
            }

            Size = new TE_Vector2(mapData.Count, mapData[0].Length).ToGridbound()*2;
        }

        private TE_Vector2 FindAreaRight(List<string[]> mapData, int x, int y, string[] acceptedValues)
        {
            var collisionArea = new TE_Vector2(1, 1);

            //Look to the right
            for (int i = x + 1; i < mapData[y].Length; i++)
            {
                string charRight = mapData[y][i];
                if (acceptedValues.Contains(charRight))
                    collisionArea.X++;
                else
                    break;
            }
            //Look down
            for (int i = y + 1; i < mapData.Count; i++)
            {
                bool increase = true;
                for (int j = 0; j < collisionArea.X; j++)
                {
                    string charRight = mapData[i][x + j];
                    if(!acceptedValues.Contains(charRight))
                    {
                        increase = false;
                        break;
                    }
                }

                if (increase)
                    collisionArea.Y++;
                else
                    break;
            }

            return collisionArea;
        }

        private TE_Vector2 FindAreaDown(List<string[]> mapData, int x, int y, string[] acceptedValues)
        {
            var collisionArea = new TE_Vector2(1, 1);
            //Look down
            for (int i = y + 1; i < mapData.Count; i++)
            {
                string charRight = mapData[i][x];
                if(acceptedValues.Contains(charRight))
                    collisionArea.Y++;
                else
                    break;
            }
            //Look to the right
            for (int i = x + 1; i < mapData[y].Length; i++)
            {
                bool increase = true;
                for (int j = 0; j < collisionArea.Y; j++)
                {
                    string charRight = mapData[y + j][i];
                    if(!acceptedValues.Contains(charRight))
                    {
                        increase = false;
                        break;
                    }
                }

                if (increase)
                    collisionArea.X++;
                else
                    break;
            }

            return collisionArea;
        }

        public TE_Vector2[] GetSpawnPoints()
        {
            return _spawnPoints.ToArray();
        }

        private void ReadMapCollisionData(string mapName)
        {
            var mapData = new List<string[]>();
            using (var sr = new StreamReader("Content/Maps/" + mapName + ".codt"))
            {
                while (!sr.EndOfStream)
                {
                    string readLine = sr.ReadLine();
                    if (readLine != null) mapData.Add(readLine.Split(' '));
                }
            }

            for (int y = 0; y < mapData.Count; y++)
            {
                for (int x = 0; x < mapData[y].Length; x++)
                {
                    string c = mapData[y][x];

                    switch (c)
                    {
                        case "C":
                            Collisions.Add(new TE_Rectangle(x, y, FindAreaRight(mapData, x, y, WallCharacters)).ToGridBound());
                            break;
                        case "D":
                            Collisions.Add(new TE_Rectangle(x, y, FindAreaDown(mapData, x, y, WallCharacters)).ToGridBound());
                            break;
                        case "E":
                            Collisions.Add(new TE_Rectangle(x, y, FindAreaRight(mapData, x, y, WallCharacters)).ToGridBound());
                            Collisions.Add(new TE_Rectangle(x, y, FindAreaDown(mapData, x, y, WallCharacters)).ToGridBound());
                            break;
                        case "X":
                            Holes.Add(new TE_Rectangle(x + 0.5f, y + 0.5f, FindAreaRight(mapData, x, y, HoleCharacters) - new TE_Vector2(1, 1)).ToGridBound());
                            break;
                        case "+":
                            _spawnPoints.Add(new TE_Vector2(x, y));
                            break;
                        case "B":
                            AddItem(x, y, ItemType.Bomb);
                            break;
                        case "R":
                            AddItem(x, y, ItemType.Random);
                            break;
                        case "S":
                            AddItem(x, y, ItemType.Shuriken);
                            break;
                        case "T":
                            AddItem(x, y, ItemType.Torch);
                            break;
                    }
                }
            }
        }

        private void AddWall(float positionX, float positionY, Enum wallType)
        {
            BackgroundLayer.AddChildren(new ImageContainer
                                            {
                                                Sprite = GetSpriteFromEnum(wallType),
                                                PositionGridBound = new TE_Vector2(positionX, positionY)
                                            });
        }

        private void AddWall(float positionX, float positionY, Enum wallType, TE_Vector2 area)
        {
            BackgroundLayer.AddChildren(new RepeatingImage
                                            {
                                                Sprite = GetSpriteFromEnum(wallType),
                                                PositionGridBound = new TE_Vector2(positionX, positionY),
                                                Area = area
                                            });
        }

        protected abstract Sprite GetSpriteFromEnum(Enum wallType);

        public void AddParticle(Container particle)
        {
            ParticleLayer.AddChildren(particle);
        }

        public void AddPlayer(PlayerDefinition player, TE_Vector2 hudPosition, TE_Vector2 hudSize)
        {
            var p = new PlayerContainer(GetSpawnPoints()[player.Id - 1], player.Name, player.Input, hudPosition, hudSize, player.Team);
            Players.Add(p);
            PlayerLayer.AddChildren(p);
        }


        private void ShowCollisionRectangle()
        {
            foreach (TE_Rectangle r in GetCollision())
            {
                DebugCollisionLayer.AddChildren(new RectanglePrimitive
                                                    {
                                                        Shape = r.AsGridBoundRectangle(),
                                                        FillColor = new Color(Color.Red, 100)
                                                    });
            }
            foreach (TE_Rectangle r in GetHoles())
            {
                DebugCollisionLayer.AddChildren(new RectanglePrimitive
                                                    {
                                                        Shape = r.AsGridBoundRectangle(),
                                                        FillColor = new Color(Color.Pink, 100)
                                                    });
            }
        }

        

        public TE_Vector2 ResolveCollision(TE_Rectangle oldPositionArea, TE_Rectangle newPositionArea,
                                           TE_Vector2 velocity, out TE_Vector2 normal)
        {
            const float collisionOffset = 0.5f;
            TE_Vector2 result = null;
            var normalResult = new TE_Vector2();

            foreach (TE_Rectangle wall in GetCollision())
            {
                if (CollisionHelper.Intersects(wall, newPositionArea))
                {
                    float xtime, ytime;

                    if (velocity.X > 0)
                    {
                        xtime = Math.Abs((oldPositionArea.Right - wall.Left)/velocity.X);
                    }
                    else if (velocity.X < 0)
                    {
                        xtime = Math.Abs((oldPositionArea.Left - wall.Right)/velocity.X);
                    }
                    else
                    {
                        xtime = float.MaxValue;
                    }

                    if (velocity.Y > 0)
                    {
                        ytime = Math.Abs((oldPositionArea.Bottom - wall.Top)/velocity.Y);
                    }
                    else if (velocity.Y < 0)
                    {
                        ytime = Math.Abs((oldPositionArea.Top - wall.Bottom)/velocity.Y);
                    }
                    else
                    {
                        ytime = float.MaxValue;
                    }

                    if (result == null)
                        result = newPositionArea.Position;

                    if (xtime < ytime)
                    {
                        if (velocity.X < 0)
                        {
                            result.X = wall.Right + collisionOffset;
                            normalResult.X = 1;
                        }
                        else
                        {
                            result.X = wall.Left - newPositionArea.Width - collisionOffset;
                            normalResult.X = -1;
                        }
                    }
                    else if (ytime < xtime)
                    {
                        if (velocity.Y < 0)
                        {
                            result.Y = wall.Bottom + collisionOffset;
                            normalResult.Y = 1;
                        }
                        else
                        {
                            result.Y = wall.Top - newPositionArea.Height - collisionOffset;
                            normalResult.Y = -1;
                        }
                    }

                    if ((newPositionArea.Position - result).Length > 32)
                    {
                        Console.WriteLine("");
                        Log.LogError("Old position: " + oldPositionArea);
                        Log.LogError("New position: " + newPositionArea);
                        Log.LogError("Result: " + result);
                        Log.LogError("Wall: " + wall);

                        Log.LogError("Velocity: " + velocity);

                        Log.LogError("Normal: " + normalResult);
                        Log.LogError("xTime: " + xtime);
                        Log.LogError("yTime: " + ytime);
                        continue;
                    }
                }
            }

            normal = normalResult;
            return result;
        }

        protected abstract void LoadSpriteSheet();

        protected abstract void AddTileLayerObjects();
        protected abstract void AddPointLayerObjects();
        protected abstract void AddCollisionLayerObjects();

        protected abstract Enum GetSpriteFromChar(char c);

        public List<TE_Rectangle> GetCollision()
        {
            return Collisions;
        }

        public List<TE_Rectangle> GetHoles()
        {
            return Holes;
        }

        public List<Warp> GetWarp()
        {
            return _warps;
        }

        protected void AddWarp(float positionX, float positionY, Color warpColor, float targetPositionX,
                               float targetPositionY)
        {
            var warp = new Warp(warpColor, new TE_Vector2(targetPositionX, targetPositionY))
                           {
                               PositionGridBound = new TE_Vector2(positionX, positionY)
                           };
            _warps.Add(warp);
            WarpLayer.AddChildren(warp);

            TileLayer.AddChildren(
                new Tile
                    {
                        PositionGridBound = new TE_Vector2(positionX, positionY)
                    },
                new WarpTarget
                    {
                        PositionGridBound = new TE_Vector2(targetPositionX, targetPositionY)
                    });
        }

        protected void AddPoint(float positionX, float positionY, Point.PointType type)
        {
            PointLayer.AddChildren(
                new Point(new TE_Vector2(positionX, positionY).ToGridbound(), type,
                          new TE_Vector2(Randomizer.Double().TimesTwoPi(), (float) Randomizer.Double(5, 20)),
                          Randomizer.Int(500, 1500)));
        }

        public override void Draw()
        {
            PlayerLayer.Children = PlayerLayer.Children.OrderBy(p => ((IOrderable) p).GetOrderingFactor()).ToList();

            base.Draw();
        }

        public IEnumerable<IAttackable> GetAttackbleObjects()
        {
            return PlayerLayer.Children.OfType<IAttackable>();
        }

        public void AddItem(Container item)
        {
            PlayerLayer.AddChildren(item);
        }

        protected void AddItem(float positionX, float positionY, ItemType item)
        {
            
            Collisions.Add(new TE_Rectangle(positionX, positionY, 1, 1).ToGridBound());

            switch (item)
            {
                case ItemType.Shuriken:
                    TileLayer.AddChildren(new Tile { PositionGridBound = new TE_Vector2(positionX, positionY) });
                    PlayerLayer.AddChildren(new IdleShuriken {PositionGridBound = new TE_Vector2(positionX, positionY)});
                    break;
                case ItemType.Bomb:
                    TileLayer.AddChildren(new Tile { PositionGridBound = new TE_Vector2(positionX, positionY) });
                    PlayerLayer.AddChildren(new IdleBomb {PositionGridBound = new TE_Vector2(positionX, positionY)});
                    break;
                case ItemType.Random:
                    TileLayer.AddChildren(new Tile { PositionGridBound = new TE_Vector2(positionX, positionY) });
                    PlayerLayer.AddChildren(new RandomObject {PositionGridBound = new TE_Vector2(positionX, positionY)});
                    break;
                case ItemType.Torch:
                    PlayerLayer.AddChildren(Torch.Red(new TE_Vector2(positionX, positionY)));
                    break;
            }
        }

        public void TossPoints(int pointsLost,
                               TE_Vector2 position,
                               TE_Rectangle holeSize)
        {
            int one = 0;
            int three = 0;
            int five = 0;
            int ten = 0;

            if (pointsLost*0.15f > 10)
            {
                ten = (int) (pointsLost*0.25f/10);
                pointsLost -= ten*10;
            }
            if (pointsLost*0.35f > 5)
            {
                five = (int) (pointsLost*0.35f/5);
                pointsLost -= five*5;
            }
            if (pointsLost*0.55f > 3)
            {
                three = (int) (pointsLost*0.55f/3);
                pointsLost -= three*3;
            }
            one = pointsLost;

            for (int i = 0; i < one; i++)
            {
                if(holeSize != null)
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.One,
                                  new TE_Vector2(((double) i/one + 0.3f).TimesTwoPi()), 500, holeSize));
                else
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.One,
                                  new TE_Vector2(((double) i/one + 0.3f).TimesTwoPi(), (float) Randomizer.Double(10, 19)),
                                  500));
            }
            for (int i = 0; i < three; i++)
            {
                if (holeSize != null)
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.Three,
                                  new TE_Vector2(((double)i / three + 0.2f).TimesTwoPi()), 500, holeSize));
                else
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.Three,
                                  new TE_Vector2(((double)i / three + 0.2f).TimesTwoPi(), (float)Randomizer.Double(7, 15)),
                                  500));
            }
            for (int i = 0; i < five; i++)
            {
                if (holeSize != null)
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.Five,
                                  new TE_Vector2(((double)i / five + 0.1f).TimesTwoPi()), 500, holeSize));
                else
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.Five,
                                  new TE_Vector2(((double)i / five + 0.1f).TimesTwoPi(), (float)Randomizer.Double(5, 11)),
                                  500));
            }
            for (int i = 0; i < ten; i++)
            {
                if (holeSize != null)
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.Ten,
                                  new TE_Vector2(((double)i / ten).TimesTwoPi()), 500, holeSize));
                else
                    PointLayer.AddChildren(
                        new Point(new TE_Vector2(position), Point.PointType.Ten,
                                  new TE_Vector2(((double)i / ten).TimesTwoPi(), (float)Randomizer.Double(3, 7)), 500));
            }
        }

        public void Attack(BaseAttack attackObject, TE_Rectangle boundingBox)
        {
            var attackable = Playscreen.Map.GetAttackbleObjects().ToList();

            foreach (var target in attackable.Where(target => CollisionHelper.Intersects(target.GetBoundingBoxCollision(), boundingBox)))
            {
                attackObject.Attack(target);
            }
        }

        public List<ICollectible> Collectibles()
        {
            return PointLayer.Children.OfType<ICollectible>()
                .Union(PlayerLayer.Children.OfType<ICollectible>()).ToList();
        }

        public bool CollidesWithWall(TE_Vector2 position)
        {
            return GetCollision().Any(wall => CollisionHelper.Intersects(position, wall));
        }

        public void TimesUp()
        {
            foreach (var player in Players)
            {
                player.TimesUp();
            }
        }
    }
}